# gozerbot/fleet.py
#
#

""" fleet is a list of bots. """

__copyright__ = 'this file is in the public domain'

# ==============
# IMPORT SECTION

# gozerbot imports

from gozerbot.datadir import datadir
from utils.exception import handle_exception
from utils.generic import waitforqueue
from utils.log import rlog
from utils.locking import lockdec
from threads.thr import start_new_thread, threaded
from config import Config, fleetbotconfigtxt, config
from users import users
from plugins import plugins
from simplejson import load

# basic imports

import Queue, os, types, threading, time, pickle, glob, logging, shutil, thread

# END IMPORT

# ============
# LOCK SECTION

fleetlock = thread.allocate_lock()
fleetlocked = lockdec(fleetlock)

# END LOCK
# ========

## START

class FleetBotAlreadyExists(Exception):
    pass


class Fleet(object):

    """
        a fleet contains multiple bots (list of bots). used the datadir
        set in gozerbot/datadir.py

    """

    def __init__(self):
        self.datadir = datadir + os.sep + 'fleet'
        if hasattr(os, 'mkdir'):
            if not os.path.exists(self.datadir):
                os.mkdir(self.datadir)
        self.startok = threading.Event()
        self.bots = []

    def getfirstbot(self):

        """
            return the main bot of the fleet.

            :rtype: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.getfirstbot

        """
        self.startok.wait()
        return self.bots[0]

    def getfirstjabber(self):

        """
            return the first jabber bot of the fleet.

            :rtype: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.getfirstjabber

        """

        self.startok.wait()

        for bot in self.bots:
            if bot.type == 'xmpp':
               return bot
        
    def size(self):

        """
             return number of bots in fleet.

             :rtype: integer

             .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.size

        """

        return len(self.bots)

    def resume(self, sessionfile):

        """
            resume bot from session file.

            :param sessionfile: filename of the session data file
            :type sessionfile: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.resume
        """

        # read JSON session file
        session = load(open(sessionfile))

        #  resume bots in session file
        for name in session['bots'].keys():
            reto = None
            if name == session['name']:
                reto = session['channel']
            start_new_thread(self.resumebot, (name, session['bots'][name], reto))

        # allow 5 seconds for bots to resurrect
        time.sleep(5)

        # set start event
        self.startok.set()

    def makebot(self, name, cfg=None):

        """
            create a bot .. use configuration if provided.

            :param name: the name of the bot
            :type name: string
            :param cfg: configuration file for the bot
            :type cfg: gozerbot.config.Config

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.makebot

        """

        if self.byname(name):
            raise FleetBotAlreadyExists("there is already a %s bot in the fleet" % name)

        rlog(10, 'fleet', 'making bot')
        bot = None

        # if not config create a default bot
        if not cfg:
            cfg = Config(self.datadir + os.sep + name, 'config', inittxt=fleetbotconfigtxt)
            cfg.save()

        # create bot based on type 
        if cfg['type'] == 'irc':
            from gozerbot.irc.bot import Bot
            bot = Bot(name, cfg)
        elif cfg['type'] == 'xmpp' or cfg['type'] == 'jabber':
            from gozerbot.xmpp.bot import Bot
            bot = Bot(name, cfg)
        elif cfg['type'] == 'gozernet':
            from gozerbot.gozernet.bot import GozerNetBot
            bot = GozerNetBot(name, cfg)
        else:
            rlog(10, 'fleet', '%s .. unproper type: %s' % (cfg['name'], cfg['type']))

        # set bot name and initialize bot
        if bot:
            cfg['name'] = bot.name = name
            self.initbot(bot)
            return bot

        # failed to created the bot
        raise Exception("can't make %s bot" % name)

    def resumebot(self, botname, data={}, printto=None):

        """
            resume individual bot.

            :param botname: name of the bot to resume
            :type botname: string
            :param data: resume data
            :type data: dict
            :param printto: whom to reply to that resuming is done
            :type printto: nick or JID

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.resumebot

        """

        # see if we need to exit the old bot
        oldbot = self.byname(botname)
        if oldbot:
            oldbot.exit()

        # recreate config file of the bot
        cfg = Config(datadir + os.sep + 'fleet' + os.sep + botname, 'config')

        # make the bot and resume (IRC) or reconnect (Jabber)
        bot = self.makebot(botname, cfg)
        rlog(100, 'fleet', 'bot made: %s' % str(bot))

        if bot:
            if oldbot:
                self.replace(oldbot, bot)
            else:
                self.bots.append(bot)

            if not bot.jabber:
                bot._resume(data, printto)
            else:
                start_new_thread(bot.connectwithjoin, ())

    def start(self, botlist=[], enable=False):

        """
            startup the bots.

            :param botlist: list of bots to start .. if not provided the bots in the gozerdata/fleet dir will be used
            :type botlist: list
            :param enable: whether the bot should be enabled 
            :type enable: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.start

        """

        # scan the fleet datadir for bots
        dirs = []
        got = []
        for bot in botlist:
            dirs.append(self.datadir + os.sep + bot)

        if not dirs:
            dirs = glob.glob(self.datadir + os.sep + "*")

        for fleetdir in dirs:

            if fleetdir.endswith('fleet'):
                continue

            rlog(10, 'fleet', 'found bot: ' + fleetdir)
            cfg = Config(fleetdir, 'config')

            if not cfg:
                rlog(10, 'fleet', "can't read %s config file" % fleetdir)
                continue

            name = fleetdir.split(os.sep)[-1]

            if not name:
                rlog(10, 'fleet', "can't read botname from %s config file" % \
fleetdir)
                continue

            if not enable and not cfg['enable']:
                rlog(10, 'fleet', '%s bot is disabled' % name)
                continue
            else:
                rlog(10, 'fleet', '%s bot is enabled' % name)

            if not name in fleetdir:
                rlog(10, 'fleet', 'bot name in config file doesnt match dir name')
                continue

            try:
                bot = self.makebot(name, cfg)
            except FleetBotAlreadyExists:
                rlog(10, 'fleet', 'there is already a fleetbot with the name %s' % name)
                continue

            if bot:
                self.addbot(bot)
                start_new_thread(bot.connectwithjoin, ())
                got.append(bot)

        # set startok event
        self.startok.set()

        return got

    def save(self):

        """
            save fleet data and call save on all the bots.

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.save

        """

        for i in self.bots:

            try:
                i.save()
            except Exception, ex:
                handle_exception()

    def avail(self):

        """
            show available fleet bots.

            :rtype: list

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.avail

        """

        return os.listdir(self.datadir)

    def list(self):

        """
            return list of bot names.

            :rtype: list

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.list

        """

        result = []

        for i in self.bots:
            result.append(i.name)

        return result

    def stopall(self):

        """ 
            call stop() on all fleet bots.

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.stopall

        """

        for i in self.bots:

            try:
                i.stop()
            except:
                pass

    def byname(self, name):

        """
            return bot by name.

            :param name: name of the bot
            :type name: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.byname

        """

        for i in self.bots:
            if name == i.name:
                return i

    def replace(self, name, bot):

        """
            replace bot with a new bot.

            :param name: name of the bot to replace
            :type name: string
            :param bot: bot to replace old bot with
            :type bot: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.replace

        """

        for i in range(len(self.bots)):
            if name == self.bots[i].name:
                self.bots[i] = bot
                return

    def initbot(self, bot):

        """
            initialise a bot.

            :param bot: bot to initialise
            :type bot: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.initbot

        """

        if bot not in self.bots:

            if not os.path.exists(self.datadir + os.sep + bot.name):
                os.mkdir(self.datadir + os.sep + bot.name)

            if type(bot.cfg['owner']) == types.StringType or type(bot.cfg['owner']) == types.UnicodeType:
                bot.cfg['owner'] = [bot.cfg['owner'], ]
                bot.cfg.save()

            users.make_owner(config['owner'] + bot.cfg['owner'])
            rlog(10, 'fleet', 'added bot: ' + bot.name)

    @fleetlocked
    def addbot(self, bot):

        """
            add a bot to the fleet .. remove all existing bots with the 
            same name.

            :param bot: bot to add
            :type bot: gozerbot.botbase.BotBase
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.addbot
            
        """

        if bot:

            for i in range(len(self.bots)-1, -1, -1):
                if self.bots[i].name == bot.name:
                    rlog(10, 'fleet', 'removing %s from fleet' % bot.name)
                    del self.bots[i]

            rlog(10, 'fleet', 'adding %s' % bot.name)
            self.bots.append(bot)
            return True

        return False

    def connect(self, name):

        """
            connect bot to the server.

            :param name: name of the bot
            :type name: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.connect

        """

        for i in self.bots:

            if i.name == name:
                got = i.connect()

                if got:
                    start_new_thread(i.joinchannels, ())
                    return True
                else:
                    return False

    @fleetlocked
    def delete(self, name):

        """
            delete bot with name from fleet.

            :param name: name of bot to delete
            :type name: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.delete

        """

        for i in self.bots:

            if i.name == name:
                i.exit()
                self.remove(i)
                i.cfg['enable'] = 0
                i.cfg.save()
                rlog(10, 'fleet', '%s disabled' % i.name)
                return True

        return False


    def remove(self, bot):

        """
            delete bot by object.

            :param bot: bot to delete
            :type bot: gozerbot.botbase.BotBase
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.remove

        """

        try:
            self.bots.remove(bot)
            return True
        except ValueError:
            return False

    def exit(self, name=None, jabber=False):

        """
            call exit on all bots. if jabber=True only jabberbots will exit.

            :param name: name of the bot to exit. if not provided all bots will exit.
            :type name: string
            :param jabber: flag to set when only jabberbots should exit
            :type jabber: boolean
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.exit

        """
        
        if not name:
            threads = []

            for i in self.bots:
                if jabber and not i.jabber:
                    pass
                else:
                    threads.append(start_new_thread(i.exit, ()))

            for thr in threads:
                thr.join()

            return


        for i in self.bots:

            if i.name == name:
                try:
                    i.exit()
                except:
                    handle_exception()
                self.remove(i)
                return True

        return False

    def cmnd(self, event, name, cmnd):
 
        """
            do command on a bot.

            :param event: event to pass on to the dispatcher
            :type event: gozerbot.event.EventBase
            :param name: name of the bot to pass on to the dispatcher
            :type name: string
            :param cmnd: command to execute on the fleet bot
            :type cmnd: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.cmnd

        """

        bot = self.byname(name)

        if not bot:
            return 0

        from gozerbot.eventbase import EventBase
        j = plugins.clonedevent(bot, event)
        j.onlyqueues = True
        j.txt = cmnd
        q = Queue.Queue()
        j.queues = [q]
        j.speed = 3
        start_new_thread(plugins.trydispatch, (bot, j))
        result = waitforqueue(q)

        if not result:
            return

        res = ["<%s>" % bot.name, ]
        res += result
        event.reply(res)

    def cmndall(self, event, cmnd):

        """
            do a command on all bots.

            :param event: event to pass on to dispatcher
            :type event: gozerbot.eventbase.EventBase
            :param cmnd: the command string to execute
            :type cmnd: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.cmndall

        """

        threads = []

        for i in self.bots:
            thread = start_new_thread(self.cmnd, (event, i.name, cmnd))
            threads.append(thread)

        for i in threads:
            i.join()

    def broadcast(self, txt):

        """
            broadcast txt to all bots.

            :param txt: text to broadcast on all bots
            :type txt: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.broadcast

        """

        for i in self.bots:
            i.broadcast(txt)

# ============
# INIT SECTION


# main fleet object
fleet = Fleet()

# END INIT
# ========
